package org.hepeng.commons.web.filter;

import lombok.Data;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.hepeng.commons.web.http.BodyCachingHttpServletRequestWrapper;
import org.hepeng.commons.web.http.BodyCachingHttpServletResponseWrapper;
import org.hepeng.commons.web.util.HttpRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @author he peng
 */

@Data
public class HttpDebugFilter extends OncePerRequestFilter {

    private static final Logger LOG = LoggerFactory.getLogger(HttpDebugFilter.class);

    private Set<String> skipUrls;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (this.skipUrls.contains(request.getRequestURI())) {
            filterChain.doFilter(request , response);
            return;
        }

        BodyCachingHttpServletResponseWrapper responseWrapper = new BodyCachingHttpServletResponseWrapper(response);
        boolean isFirstRequest = !isAsyncDispatch(request);
        boolean shouldLog = shouldLog(request);
        if (shouldLog && isFirstRequest) {
            beforeRequest(request , responseWrapper);
        }

        try {
            filterChain.doFilter(request , responseWrapper);
        }
        finally {
            if (shouldLog && !isAsyncStarted(request)) {
                afterRequest(request , responseWrapper);
            }
        }
    }

    private void beforeRequest(HttpServletRequest request , BodyCachingHttpServletResponseWrapper response) {
        if (LOG.isDebugEnabled()) {
            LOG.debug(createRequestLogMessage(request));
        }
    }

    protected void afterRequest(HttpServletRequest request , BodyCachingHttpServletResponseWrapper response) {
        if (LOG.isDebugEnabled()) {
            LOG.debug(createResponseLogMessage(request , response));
        }
    }

    protected String createRequestLogMessage(HttpServletRequest request) {
        ServletServerHttpRequest serverHttpRequest = new ServletServerHttpRequest(request);
        Map<String , String> requestParameter = new HashMap<>();
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (Objects.nonNull(parameterMap)) {
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                requestParameter.put(entry.getKey() , Arrays.deepToString(entry.getValue()));
            }
        }

        String requestPayload = "";
        if (! HttpRequestUtils.isMultipart(request)) {
            requestPayload = getRequestPayload(request);
        }

        Cookie[] cookies = request.getCookies();
        List<String> cookieMsgs = new ArrayList<>();
        if (ArrayUtils.isNotEmpty(cookies)) {
            for (Cookie cookie : cookies) {
                String cookieMsg = cookie.getName() + " = " + cookie.getValue();
                cookieMsgs.add(cookieMsg);
            }
        }

        String logMsg = new StringBuilder("\r\n ******************** Http Request ******************** \r\n")
                .append("Request URL : ").append(request.getRequestURL())
                .append("\r\n")
                .append("Method : ").append(serverHttpRequest.getMethod())
                .append("\r\n")
                .append("Session ID : ").append(request.getSession(false))
                .append("\r\n")
                .append("Client IP : ").append(serverHttpRequest.getRemoteAddress())
                .append("\r\n")
                .append("Parameter : ").append(requestParameter)
                .append("\r\n")
                .append("Content Type : ").append(request.getContentType())
                .append("\r\n")
                .append("Headers : ").append(serverHttpRequest.getHeaders())
                .append("\r\n")
                .append("Cookie : ").append(cookieMsgs)
                .append("\r\n")
                .append("Payload : ").append(requestPayload)
                .toString();

        return logMsg;
    }

    private String getRequestPayload(HttpServletRequest request) {
        try {
            BodyCachingHttpServletRequestWrapper readableRequest = new BodyCachingHttpServletRequestWrapper(request);
            return StringUtils.newStringUtf8(readableRequest.getBody());
        } catch (IOException e) {
            LOG.error(e.getMessage() , e);
        }
        return "";
    }

    private String getResponsePayload(BodyCachingHttpServletResponseWrapper responseWrapper) {
        return StringUtils.newString(responseWrapper.getBody() , responseWrapper.getCharacterEncoding());
    }

    protected String createResponseLogMessage(HttpServletRequest request , BodyCachingHttpServletResponseWrapper response) {
        ServletServerHttpResponse serverHttpResponse = new ServletServerHttpResponse(response);
        ServletServerHttpRequest serverHttpRequest = new ServletServerHttpRequest(request);
        Map<String , String> headers = new HashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        if (CollectionUtils.isNotEmpty(headerNames)) {
            for (String headerName : headerNames) {
                headers.put(headerName , response.getHeader(headerName));
            }
        }

        String logMsg = new StringBuilder("\r\n ******************** Http Response ******************** \r\n")
                .append("Request URL : ").append(request.getRequestURL())
                .append("\r\n")
                .append("Method : ").append(serverHttpRequest.getMethod())
                .append("\r\n")
                .append("Http Status : ").append(response.getStatus())
                .append("\r\n")
                .append("Session ID : ").append(request.getSession(false))
                .append("\r\n")
                .append("Content Type : ").append(response.getContentType())
                .append("\r\n")
                .append("Headers : ").append(headers)
                .append("\r\n")
                .append("Payload : ").append(getResponsePayload(response))
                .toString();

        return logMsg;
    }

    protected boolean shouldLog(HttpServletRequest request) {
        return LOG.isDebugEnabled();
    }
}
